
/****************************************************************************/

static inline int allocProcess(kernelProcess **processPointer)
{
	// This routine is used to allocate new process control memory.  It
	// should be passed a reference to a pointer that will point to
	// the new process, if allocated successfully.

	int status = 0;
	kernelProcess *newProcess = NULL;

	// Make sure the pointer->pointer parameter we were passed isn't NULL
	if (processPointer == NULL)
		return (status = ERR_NULLPARAMETER);
  
	newProcess = kernelMalloc(sizeof(kernelProcess));
	if (newProcess == NULL)
		return (status = ERR_MEMORY);

	// Success.  Set the pointer for the calling process
	*processPointer = newProcess;
	return (status = 0);
}

/****************************************************************************/

static inline int freeProcess(kernelProcess *killProcess)
{
	// This routine is used to free process control memory.  It
	// should be passed the process Id of the process to kill.  It
	// returns 0 on success, negative otherwise

	int status = 0;

	status = kernelFree((void *) killProcess);

	return (status);
}

/****************************************************************************/

static int addProcessToQueue(kernelProcess *targetProcess)
{
	// This routine will add a process to the task queue.  It returns zero 
	// on success, negative otherwise

	ASSERT_LOCK_PROCESS_QUEUE();

	int status = 0;
	int count;

	if (targetProcess == NULL)
		// The process does not exist, or is not accessible
		return (status = ERR_NOSUCHPROCESS);

	// Make sure the priority is a legal value
	if ((targetProcess->priority < 0) || (targetProcess->priority > (PRIORITY_LEVELS - 1)))
		// The process' priority is an illegal value
		return (status = ERR_INVALID);

	// Search the process queue to make sure it isn't already present
	for (count = 0; count < numQueued; count ++)
		if (processQueue[count] == targetProcess)
			// Oops, it's already there
			return (status = ERR_ALREADY);

	// OK, now we can add the process to the queue
	processQueue[numQueued++] = targetProcess;

	// Done
	return (status = 0);
}

/****************************************************************************/

static int removeProcessFromQueue(kernelProcess *targetProcess)
{
	// This routine will remove a process from the task queue.  It returns zero 
	// on success, negative otherwise

	ASSERT_LOCK_PROCESS_QUEUE();

	int status = 0;
	int processPosition = 0;
	int count;

	if (targetProcess == NULL)
		// The process does not exist, or is not accessible
		return (status = ERR_NOSUCHPROCESS);

	// Search the queue for the matching process
	for (count = 0; count < numQueued; count ++)
		if (processQueue[count] == targetProcess)
		{
			processPosition = count;
			break;
		}

	// Make sure we found the process
	if (processQueue[processPosition] != targetProcess)
		// The process is not in the task queue
		return (status = ERR_NOSUCHPROCESS);

	// Subtract one from the number of queued processes
	numQueued -= 1;

	// OK, now we can remove the process from the queue.  If there are one
	// or more remaining processes in this queue, we will shorten the queue
	// by moving the LAST process into the spot we're vacating
	if ((numQueued > 0) && (processPosition != numQueued))
		processQueue[processPosition] = processQueue[numQueued];

	// Done
	return (status = 0);
}

/****************************************************************************/

static int createTaskStateSegment(kernelProcess *theProcess)
{
	// This function will create a TSS (Task State Segment) for a new
	// process based on the attributes of the process.  This function relies
	// on the privilege, userStackSize, and superStackSize attributes having 
	// been previously set.  Returns 0 on success, negative on error.

	int status = 0;

	// Get a free descriptor for the process' TSS
	status = kernelDescriptorRequest(&(theProcess->tssSelector));
	if ((status < 0) || (theProcess->tssSelector == 0))
		// Crap.  An error getting a free descriptor.
		return (status);

	// Fill in the process' Task State Segment descriptor
	status = kernelDescriptorSet(
		theProcess->tssSelector, // TSS selector number
		&(theProcess->taskStateSegment), // Starts at...
		sizeof(kernelTSS),      // Limit of a TSS segment
		1,                      // Present in memory
		PRIVILEGE_SUPERVISOR,   // TSSs are supervisor privilege level
		0,                      // TSSs are system segs
		0xB,                    // TSS, 32-bit, busy
		0,                      // 0 for SMALL size granularity
		0);                     // Must be 0 in TSS
	if (status < 0)
	{
		// Crap.  An error getting a free descriptor.
		kernelDescriptorRelease(theProcess->tssSelector);
		return (status);
	}

	// Now, fill in the TSS (Task State Segment) for the new process.  Parts
	// of this will be different depending on whether this is a user
	// or supervisor mode process

	kernelMemClear((void *) &(theProcess->taskStateSegment), sizeof(kernelTSS));

	// Set the IO bitmap's offset
	theProcess->taskStateSegment.IOMapBase = IOBITMAP_OFFSET;

	if (theProcess->processorPrivilege == PRIVILEGE_SUPERVISOR)
	{
		theProcess->taskStateSegment.CS = PRIV_CODE;
		theProcess->taskStateSegment.DS = PRIV_DATA;
		theProcess->taskStateSegment.SS = PRIV_STACK;
	}
	else
	{
		theProcess->taskStateSegment.CS = USER_CODE;
		theProcess->taskStateSegment.DS = USER_DATA;
		theProcess->taskStateSegment.SS = USER_STACK;

		// Turn off access to all I/O ports by default
		kernelMemSet((void *) theProcess->taskStateSegment.IOMap, 0xFF, PORTS_BYTES);
	}

	theProcess->taskStateSegment.ES = theProcess->taskStateSegment.DS;
	theProcess->taskStateSegment.FS = theProcess->taskStateSegment.DS;
	theProcess->taskStateSegment.GS = theProcess->taskStateSegment.DS;

	theProcess->taskStateSegment.ESP =
		((unsigned) theProcess->userStack + (theProcess->userStackSize - sizeof(int)));

	if (theProcess->processorPrivilege != PRIVILEGE_SUPERVISOR)
	{
		theProcess->taskStateSegment.SS0 = PRIV_STACK;
		theProcess->taskStateSegment.ESP0 =
			((unsigned) theProcess->superStack + (theProcess->superStackSize - sizeof(int)));
	}

	theProcess->taskStateSegment.EFLAGS = 0x00000202; // Interrupts enabled
	theProcess->taskStateSegment.CR3 = (unsigned)
	theProcess->pageDirectory->physical;

	// All remaining values will be NULL from initialization.  Note that this 
	// includes the EIP.

	// Return success
	return (status = 0);
}

/****************************************************************************/

static int createNewProcess(const char *name, int priority, int privilege, processImage *execImage, int newPageDir)
{
	// This function is used to create a new process in the process
	// queue.  It makes a "defaults" kind of process -- it sets up all of
	// the process' attributes with default values.  If the calling process
	// wants something different, it should reset those attributes afterward.
	// If successful, it returns the processId of the new process.  Otherwise, 
	// it returns negative.

	int status = 0;
	kernelProcess *newProcess = NULL;
	void *stackMemoryAddr = NULL;
	void *physicalCodeData = NULL;
	int *args = NULL;
	char **argv = NULL;
	int argSpaceSize = 0;
	char *argSpace = NULL;
	char *newArgAddress = NULL;
	int length = 0;
	int count;

	// Don't bother checking the parameters, as the external functions 
	// should have done this already.

	// alloc process control memory
	status = allocProcess(&newProcess);
	if (status < 0)
		return (status);

	if (newProcess == NULL)
	{
		kernelError(kernel_error, "New process structure is NULL");
		return (status = ERR_NOFREE);
	}

	kernelMemClear((void *) newProcess, sizeof(kernelProcess));

	// Fill in the process' Id number
	newProcess->processId = processIdCounter++;

	// By default, the type is process
	newProcess->type = proc_normal;

	// Now, if the process Id is KERNELPROCID, then we are creating the
	// kernel process, and it will be its own parent.  Otherwise, get the
	// current process and make IT be the parent of this new process
	if (newProcess->processId == KERNELPROCID)
	{
		newProcess->parentProcessId = newProcess->processId;
		newProcess->userId = 1;   // Admin
		// Give it "/" as current working directory
		strncpy((char *) newProcess->currentDirectory, "/", 2);
	}
	else
	{
		// Make sure the current process isn't NULL
		if (kernelCurrentProcess == NULL)
		{
			kernelError(kernel_error, "No current process!");
			freeProcess(newProcess);
			return (status = ERR_NOSUCHPROCESS);
		}

		// Fill in the process' parent Id number
		newProcess->parentProcessId = kernelCurrentProcess->processId;
		// Fill in the process' user Id number
		newProcess->userId = kernelCurrentProcess->userId;
		// Fill in the current working directory
		strncpy((char *) newProcess->currentDirectory,
			(char *) kernelCurrentProcess->currentDirectory,
			MAX_PATH_LENGTH);
		newProcess->currentDirectory[MAX_PATH_LENGTH - 1] = '\0';
	}

	// Fill in the process name
	strncpy((char *) newProcess->processName, name, MAX_PROCNAME_LENGTH);
	newProcess->processName[MAX_PROCNAME_LENGTH - 1] = '\0';

	// Fill in the process' priority level
	newProcess->priority = priority;

	// Fill in the process' privilege level
	newProcess->privilege = privilege;

	// Fill in the process' processor privilege level.  The kernel and its
	// threads get PRIVILEGE_SUPERVISOR, all others get PRIVILEGE_USER.
	if (execImage->virtualAddress >= (void *) KERNEL_VIRTUAL_ADDRESS)
		newProcess->processorPrivilege = PRIVILEGE_SUPERVISOR;
	else
		newProcess->processorPrivilege = PRIVILEGE_USER;

	// The amount of time since started (now)
	newProcess->startTime = kernelGetMicrosecondSoftTimer();

	// The thread's initial state will be "stopped"
	newProcess->state = proc_stopped;

	ACQUIRE_LOCK_PROCESS_QUEUE();

	// Add the process to the process queue so we can continue whilst doing
	// things like changing memory ownerships
	status = addProcessToQueue(newProcess);
	if (status < 0)
	{
		// Not able to queue the process.
		RELEASE_LOCK_PROCESS_QUEUE();
		freeProcess(newProcess);
		return (status);
	}

	// Do we need to create a new page directory and a set of page tables for 
	// this process?
	if (newPageDir)
	{
		if (!execImage->virtualAddress || !execImage->code || 
			!execImage->codeSize || !execImage->data || !execImage->dataSize ||
			!execImage->imageSize)
		{
			kernelError(kernel_error, "New process \"%s\" executable image is missing data", name);
			removeProcessFromQueue(newProcess);
			RELEASE_LOCK_PROCESS_QUEUE();
			freeProcess(newProcess);
			return (status = ERR_NODATA);
		}

		// We need to make a new page directory, etc.
		newProcess->pageDirectory =
			kernelPageNewDirectory(newProcess->processId);
		if (newProcess->pageDirectory == NULL)
		{
			// Not able to setup a page directory
			removeProcessFromQueue(newProcess);
			RELEASE_LOCK_PROCESS_QUEUE();
			freeProcess(newProcess);
			return (status = ERR_NOVIRTUAL);
		}

		// Get the physical address of the code/data
		physicalCodeData =
			kernelPageGetPhysical(newProcess->parentProcessId, execImage->code);

		// Make the process own its code/data memory.  Don't remap it yet
		// because we want to map it at the requested virtual address.
		status = kernelMemoryChangeOwner(newProcess->parentProcessId,
			newProcess->processId, 0, execImage->code, NULL);
		if (status < 0)
		{
			// Couldn't make the process own its memory
			removeProcessFromQueue(newProcess);
			RELEASE_LOCK_PROCESS_QUEUE();
			freeProcess(newProcess);
			return (status);
		}

		// Remap the code/data to the requested virtual address.
		status = kernelPageMap(newProcess->processId, physicalCodeData,
			execImage->virtualAddress, execImage->imageSize);
		if (status < 0)
		{
			// Couldn't map the process memory
			removeProcessFromQueue(newProcess);
			RELEASE_LOCK_PROCESS_QUEUE();
			freeProcess(newProcess);
			return (status);
		}

		// Code should be read-only
		status =
			kernelPageSetAttrs(newProcess->processId, 0, PAGEFLAG_WRITABLE,
				execImage->virtualAddress, execImage->codeSize);
		if (status < 0)
		{
			removeProcessFromQueue(newProcess);
			RELEASE_LOCK_PROCESS_QUEUE();
			freeProcess(newProcess);
			return (status);
		}
	}
	else
	{
		// This process will share a page directory with its parent
		newProcess->pageDirectory =
			kernelPageShareDirectory(newProcess->parentProcessId, newProcess->processId);
		if (newProcess->pageDirectory == NULL)
		{
			removeProcessFromQueue(newProcess);
			RELEASE_LOCK_PROCESS_QUEUE();
			freeProcess(newProcess);
			return (status = ERR_NOVIRTUAL);
		}
	}

	// Give the process a stack
	newProcess->userStackSize = DEFAULT_STACK_SIZE;
	if (newProcess->processorPrivilege != PRIVILEGE_SUPERVISOR)
		newProcess->superStackSize = DEFAULT_SUPER_STACK_SIZE;

	stackMemoryAddr = kernelMemoryGet((newProcess->userStackSize + newProcess->superStackSize), "process stack");
	if (stackMemoryAddr == NULL)
	{
		// We couldn't make a stack for the new process.  Maybe the system
		// doesn't have anough available memory?
		removeProcessFromQueue(newProcess);
		RELEASE_LOCK_PROCESS_QUEUE();
		freeProcess(newProcess);
		return (status = ERR_MEMORY);
	}

	// Copy 'argc' and 'argv' arguments to the new process' stack while we
	// still own the stack memory.

	// Set pointers to the appropriate stack locations for the arguments
	args = (stackMemoryAddr + newProcess->userStackSize - (2 * sizeof(int)));

	// Calculate the amount of memory we need to allocate for argument data.
	// Leave space for pointers to the strings, since the (int argc,
	// char *argv[]) scheme means just 2 values on the stack: an integer
	// an a pointer to an array of char* pointers...
	argSpaceSize = ((execImage->argc + 1) * sizeof(char *));
	for (count = 0; count < execImage->argc; count ++)
		argSpaceSize += (strlen(execImage->argv[count]) + 1);

	// Get memory for the argument data
	argSpace = kernelMemoryGet(argSpaceSize, "process arguments");
	if (argSpace == NULL)
	{
		kernelMemoryRelease(stackMemoryAddr);
		removeProcessFromQueue(newProcess);
		RELEASE_LOCK_PROCESS_QUEUE();
		freeProcess(newProcess);
		return (status = ERR_MEMORY);
	}

	// Change ownership to the new process, and share it back with this process.
	if (kernelMemoryChangeOwner(newProcess->parentProcessId,
		newProcess->processId, 1, argSpace,
		(void **) &newArgAddress) < 0)
	{
		kernelMemoryRelease(stackMemoryAddr);
		kernelMemoryRelease(argSpace);
		removeProcessFromQueue(newProcess);
		RELEASE_LOCK_PROCESS_QUEUE();
		freeProcess(newProcess);
		return (status = ERR_MEMORY);
	}

	if (kernelMemoryShare(newProcess->processId, newProcess->parentProcessId,
		newArgAddress, (void **) &argSpace) < 0)
	{
		kernelMemoryRelease(stackMemoryAddr);
		removeProcessFromQueue(newProcess);
		RELEASE_LOCK_PROCESS_QUEUE();
		freeProcess(newProcess);
		return (status = ERR_MEMORY);
	}

	args[0] = execImage->argc;
	args[1] = (int) newArgAddress;

	argv = (char **) argSpace;
	argSpace += ((execImage->argc + 1) * sizeof(char *));
	newArgAddress += ((execImage->argc + 1) * sizeof(char *));

	// Copy the args into argv
	for (count = 0; count < execImage->argc; count ++)
	{
		strcpy((argSpace + length), execImage->argv[count]);
		argv[count] = (newArgAddress + length);
		length += (strlen(execImage->argv[count]) + 1);
	}

	// argv[argc] is supposed to be a NULL pointer, according to
	// some standard or other
	argv[args[0]] = NULL;

	// Unmap the argument space from this process
	kernelPageUnmap(newProcess->parentProcessId, argSpace, argSpaceSize);

	// Make the process own its stack memory
	status = kernelMemoryChangeOwner(newProcess->parentProcessId, 
		newProcess->processId, 1, // remap
		stackMemoryAddr, 
		(void **) &(newProcess->userStack));
	if (status < 0)
	{
		// Couldn't make the process own its memory
		kernelMemoryRelease(stackMemoryAddr);
		removeProcessFromQueue(newProcess);
		RELEASE_LOCK_PROCESS_QUEUE();
		freeProcess(newProcess);
		return (status);
	}

	// Make the topmost page of the user stack privileged, so that we have
	// have a 'guard page' that produces a page fault in case of (userspace)
	// stack overflow
	kernelPageSetAttrs(newProcess->processId, 0, PAGEFLAG_USER,
		newProcess->userStack, MEMORY_PAGE_SIZE);

	if (newProcess->processorPrivilege != PRIVILEGE_SUPERVISOR)
	{
		// Get the new virtual address of supervisor stack
		newProcess->superStack = (newProcess->userStack + DEFAULT_STACK_SIZE);

		// Make the entire supervisor stack privileged
		kernelPageSetAttrs(newProcess->processId, 0, PAGEFLAG_USER,
			newProcess->superStack, newProcess->superStackSize);
	}

	// Create the TSS (Task State Segment) for this process.
	status = createTaskStateSegment(newProcess);
	if (status < 0)
	{
		// Not able to create the TSS
		RELEASE_LOCK_PROCESS_QUEUE();
		freeProcess(newProcess);
		return (status);
	}

	// Adjust the stack pointer to account for the arguments that we copied to
	// the process' stack
	newProcess->taskStateSegment.ESP -= sizeof(int);

	// Set the EIP to the entry point
	newProcess->taskStateSegment.EIP = (unsigned) execImage->entryPoint;

	newProcess->justCreated = 1;

	kernelPerformanceCounterInc1(pcProcessesCreated);

	RELEASE_LOCK_PROCESS_QUEUE();

	// Return the processId on success.
	return (status = newProcess->processId);
}

/****************************************************************************/

static int deleteProcess(kernelProcess *killProcess)
{
	// Does all the work of actually destroyng a process when there's really
	// no more use for it.  This occurs after all descendent threads have
	// terminated, for example.

	int status = 0;

	// Processes cannot delete themselves
	if (killProcess == kernelCurrentProcess)
	{
		kernelError(kernel_error, "Process %d cannot delete itself", killProcess->processId);
		return (status = ERR_INVALID);
	}

	// We need to deallocate the TSS descriptor allocated to the process, if
	// it has one
	if (killProcess->tssSelector)
	{
		status = kernelDescriptorRelease(killProcess->tssSelector);
		// If this was unsuccessful, we don't want to continue and "lose" 
		// the descriptor
		if (status < 0)
		{
			kernelError(kernel_error, "Can't release TSS");
			return (status);
		}
	}

	// Try to close all network connections owned by this process
	status = kernelNetworkCloseAll(killProcess->processId);
	if (status < 0)
		kernelError(kernel_warn, "Can't release network connections");

	// If the process has a signal stream, destroy it
	if (killProcess->signalStream.buffer)
		kernelStreamDestroy(&(killProcess->signalStream));

	// Deallocate all memory owned by this process
	status = kernelMemoryReleaseAllByProcId(killProcess->processId);
	// Likewise, if this deallocation was unsuccessful, we don't want to
	// deallocate the process structure.  If we did, the memory would become
	// "lost".
	if (status < 0)
	{
		kernelError(kernel_error, "Can't release process memory");
		return (status);
	}

	// Delete the page table we created for this process
	status = kernelPageDeleteDirectory(killProcess->processId);
	// If this deletion was unsuccessful, we don't want to deallocate the 
	// process structure.  If we did, the page directory would become "lost".
	if (status < 0)
	{
		kernelError(kernel_error, "Can't release page directory");
		return (status);
	}

	if (fpuProcess == killProcess)
		fpuProcess = NULL;

	if (killProcess->fpuState)
	{
		kernelFree(killProcess->fpuState);
		killProcess->fpuState = 0;
	}

	// Remove the process from the multitasker's process queue.
	status = removeProcessFromQueue(killProcess);
	if (status < 0)
	{
		// Not able to remove the process
		kernelError(kernel_error, "Can't dequeue process");
		return (status);
	}

	// Finally, release the process structure
	status = freeProcess(killProcess);
	if (status < 0)
	{
		kernelError(kernel_error, "Can't release process structure");
		return (status);
	}

	return (status = 0);
}

/****************************************************************************/

static int createKernelProcess(void)
{
  // This function will create the kernel process at initialization time.
  // Returns 0 on success, negative otherwise.

  int status = 0;
  int kernelProcId = 0;
  processImage kernImage = {
    (void *) KERNEL_VIRTUAL_ADDRESS,
    kernelMain, 
    NULL, 0xFFFFFFFF,
    NULL, 0xFFFFFFFF,
    0xFFFFFFFF,
    "", 0, { NULL }
  };

  // The kernel process is its own parent, of course, and it is owned 
  // by "admin".  We create no page table, and there are no arguments.
  kernelProcId = 
    createNewProcess("kernel process", 1, PRIVILEGE_SUPERVISOR, &kernImage, 0);
  if (kernelProcId < 0)
    // Damn.  Not able to create the kernel process
    return (kernelProcId);

  // Get the pointer to the kernel's process
  kernelProc = getProcessById(kernelProcId);

  // Make sure it's not NULL
  if (kernelProc == NULL)
    // Can't access the kernel process
    return (status = ERR_NOSUCHPROCESS);

  // Interrupts are initially disabled for the kernel
  kernelProc->taskStateSegment.EFLAGS = 0x00000002;

  // Set the current process to initially be the kernel process
  kernelCurrentProcess = kernelProc;

  // Deallocate the stack that was allocated, since the kernel already
  // has one set up by the OS loader.
  kernelMemoryRelease(kernelProc->userStack);

  // Create the kernel process' environment
  status = kernelEnvironmentCreate(KERNELPROCID, (variableList *)
				   &(kernelProc->environment), NULL);
  if (status < 0)
    // Couldn't create an environment structure for this process
    return (status);

  // Make the kernel's text streams be the console streams
  kernelProc->textInputStream = kernelTextGetConsoleInput();
  kernelProc->textInputStream->ownerPid = KERNELPROCID;
  kernelProc->textOutputStream = kernelTextGetConsoleOutput();

  // Make the kernel process runnable
  kernelProc->state = proc_ready;

  // Return success
  return (status = 0);
}

/****************************************************************************/

static void incrementDescendents(kernelProcess *theProcess)
{
  // This will walk up a chain of dependent child threads, incrementing
  // the descendent count of each parent
  
  kernelProcess *parentProcess = NULL;

  if (theProcess->processId == KERNELPROCID)
    // The kernel is its own parent
    return;

  parentProcess = getProcessById(theProcess->parentProcessId);
  if (parentProcess == NULL)
    // No worries.  Probably not a problem
    return;

  parentProcess->descendentThreads++;

  if (parentProcess->type == proc_thread)
    // Do a recursion to walk up the chain
    incrementDescendents(parentProcess);

  // Done
  return;
}


static void decrementDescendents(kernelProcess *theProcess)
{
  // This will walk up a chain of dependent child threads, decrementing
  // the descendent count of each parent
  
  kernelProcess *parentProcess = NULL;

  if (theProcess->processId == KERNELPROCID)
    // The kernel is its own parent
    return;

  parentProcess = getProcessById(theProcess->parentProcessId);
  if (parentProcess == NULL)
    // No worries.  Probably not a problem
    return;

  parentProcess->descendentThreads--;

  if (parentProcess->type == proc_thread)
    // Do a recursion to walk up the chain
    decrementDescendents(parentProcess);

  // Done
  return;
}

/****************************************************************************\
	Below here, the functions are exported for external use
\****************************************************************************/

int kernelMultitaskerCreateProcess(const char *name, int privilege, processImage *execImage)
{
	// This function is called to set up an (initially) single-threaded
	// process in the multitasker.  This is the routine used by external
	// sources -- the loader for example -- to define new processes.  This 
	// new process thread we're creating will have its state set to "stopped" 
	// after this call.  The caller should use the 
	// kernelMultitaskerChangeThreadState routine to start the new thread.  
	// This function returns the processId of the new process on success, 
	// negative otherwise.

	int status = 0;
	int processId = 0;
	kernelProcess *newProcess = NULL;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);

	// Make sure the parameters are valid
	if ((name == NULL) || (execImage == NULL))
		return (status = ERR_NULLPARAMETER);

	// Make sure that an unprivileged process is not trying to create a
	// privileged one
	if ((kernelCurrentProcess->privilege == PRIVILEGE_USER) &&
	    (privilege == PRIVILEGE_SUPERVISOR))
	{
		kernelError(kernel_error, "An unprivileged process cannot create a privileged process");
		return (status == ERR_PERMISSION);
	}

	ACQUIRE_LOCK_PROCESS_QUEUE();

	// Create the new process
	processId = createNewProcess(name, PRIORITY_DEFAULT, privilege, execImage, 1); // create page directory

	// Get the pointer to the new process from its process Id
	newProcess = getProcessById(processId);
	if (newProcess == NULL)
	{
		// We couldn't get access to the new process
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status = ERR_NOCREATE);
	}

	// Create the process' environment
	status = kernelEnvironmentCreate(newProcess->processId, (variableList *)
		&(newProcess->environment), (variableList *)
		&(kernelCurrentProcess->environment));
	if (status < 0)
	{
		/* FIXME: Do we need to delete the process here? */
		newProcess->state = proc_finished;

		// Couldn't create an environment structure for this process
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status);
	}

	// Don't assign input or output streams to this process.  There are
	// multiple possibilities here, and the caller will have to either block
	// (which takes care of such things) or sort it out for themselves.

	RELEASE_LOCK_PROCESS_QUEUE();

	// Return whatever was returned by the previous call
	return (processId);
}

/****************************************************************************/

int kernelMultitaskerSpawn(void *startAddress, const char *name, int argc,
			   void *argv[])
{
	// This function is used to spawn a new thread from the current
	// process.  The function needs to be told the starting address of
	// the code to execute, and an optional argument to pass to the 
	// spawned function.  It returns the new process Id on success,
	// negative otherwise.

	int status = 0;
	int processId = 0;
	kernelProcess *newProcess = NULL;
	processImage execImage;
	int count;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);

	// Check params
	if ((startAddress == NULL) || (name == NULL))
		return (status = ERR_NULLPARAMETER);

	// If the number of arguments is not zero, make sure the arguments
	// pointer is not NULL
	if (argc && !argv)
		return (status = ERR_NULLPARAMETER);

	// Make sure the current process isn't NULL
	if (kernelCurrentProcess == NULL)
		return (status = ERR_NOSUCHPROCESS);

	kernelMemClear(&execImage, sizeof(processImage));
	execImage.virtualAddress = startAddress;
	execImage.entryPoint = startAddress;
	execImage.code = NULL;
	execImage.codeSize = 0;
	execImage.data = NULL;
	execImage.dataSize = 0;
	execImage.imageSize = 0;

	// Set up arguments
	execImage.argc = (argc + 1);
	execImage.argv[0] = (char *) name;
	for (count = 0; count < argc; count ++)
		execImage.argv[count + 1] = argv[count];

	ACQUIRE_LOCK_PROCESS_QUEUE();

	// OK, now we should create the new process
	processId = createNewProcess(name, kernelCurrentProcess->priority,
		kernelCurrentProcess->privilege,
		&execImage, 0);
	if (processId < 0)
	{
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status = processId);
	}
  
	// Get the pointer to the new process from its process Id
	newProcess = getProcessById(processId);

	// Make sure it's valid
	if (newProcess == NULL)
	{
		// We couldn't get access to the new process
		RELEASE_LOCK_PROCESS_QUEUE();
		return (status = ERR_NOCREATE);
	}

	// Change the type to thread
	newProcess->type = proc_thread;
  
	// Increment the descendent counts 
	incrementDescendents(newProcess);

	// Since we assume that the thread is invoked as a function call, 
	// subtract additional bytes from the stack pointer to account for
	// the space where the return address would normally go.
	newProcess->taskStateSegment.ESP -= sizeof(void *);

	// Copy the environment
	newProcess->environment = kernelCurrentProcess->environment;

	// The new process should share (but not own) the same text streams as the
	// parent
	newProcess->textInputStream = kernelCurrentProcess->textInputStream;
	newProcess->textOutputStream = kernelCurrentProcess->textOutputStream;

	// Make the new thread runnable
	newProcess->state = proc_ready;

	if (kernelCurrentProcess == kernelProc)
		kernelLog("Spawned kernel thread: %s", execImage.argv[0]);

	RELEASE_LOCK_PROCESS_QUEUE();

	// Return the new process' Id.
	return (processId);
}

/****************************************************************************/

int kernelMultitaskerSpawnKernelThread(void *startAddress, const char *name, 
				       int argc, void *argv[])
{
	// This function is a wrapper around the regular spawn() call, which
	// causes threads to be spawned as children of the kernel, instead of
	// children of the calling process.  This is important for threads that
	// are spawned from code which belongs to the kernel.

	int status = 0;
	//int interrupts = 0;
	kernelProcess *myProcess;

	// Make sure multitasking has been enabled
	if (!multitaskingEnabled)
		return (status = ERR_NOTINITIALIZED);

	ACQUIRE_LOCK_PROCESS_QUEUE();

	// What is the current process?
	myProcess = kernelCurrentProcess;

	// Disable interrupts while we're monkeying
	//kernelProcessorSuspendInts(interrupts);

	// Change the current process to the kernel process
	kernelCurrentProcess = kernelProc;

	// Spawn

	status = kernelMultitaskerSpawn(startAddress, name, argc, argv);

	// Reset the current process
	kernelCurrentProcess = myProcess;

	// Reenable interrupts
	//kernelProcessorRestoreInts(interrupts);

	RELEASE_LOCK_PROCESS_QUEUE();

	// Done
	return (status);
}

/****************************************************************************/

